{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mHDxn9VHjxKn"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "3x19oys5j89H"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hFDUpbtvv_3u"
      },
      "source": [
        "# Сохранение и сериализация моделей с Keras"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "V94_3U2k9rWV"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/guide/keras/save_and_serialize\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />Смотрите на TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/save_and_serialize.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Запустите в Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ru/guide/keras/save_and_serialize.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />Изучайте код на GitHub</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ru/guide/keras/save_and_serialize.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\" />Скачайте ноутбук</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fj66ZXAzrJC2"
      },
      "source": [
        "Note: Вся информация в этом разделе переведена с помощью русскоговорящего Tensorflow сообщества на общественных началах. Поскольку этот перевод не является официальным, мы не гарантируем что он на 100% аккуратен и соответствует [официальной документации на английском языке](https://www.tensorflow.org/?hl=en). Если у вас есть предложение как исправить этот перевод, мы будем очень рады увидеть pull request в [tensorflow/docs](https://github.com/tensorflow/docs) репозиторий GitHub. Если вы хотите помочь сделать документацию по Tensorflow лучше (сделать сам перевод или проверить перевод подготовленный кем-то другим), напишите нам на [docs-ru@tensorflow.org list](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ru)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZwiVWAQc9tk7"
      },
      "source": [
        "Первая часть руководства охватывает сохранение и сериализацию для Sequential и построенных с помощью Functional API моделей. API сохранения и сериализации в точности совпадают для обеих видов моделей.\n",
        "\n",
        "Сохранение для кастомных субклассов `Model` рассматривается в разделе \"Сохранение наследованных моделей\". В этом случае API немного отличается от Sequential или Functional моделей."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uqSgPMHguAAs"
      },
      "source": [
        "## Установка"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "bx5w4U5muDAo"
      },
      "outputs": [],
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals\n",
        "\n",
        "try:\n",
        "  # %tensorflow_version only exists in Colab.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow as tf\n",
        "\n",
        "tf.keras.backend.clear_session()  # Для легкого сброса состояния ноутбука."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wwCxkE6RyyPy"
      },
      "source": [
        "## Часть I: Сохранение Sequential моделей Functional моделей\n",
        "\n",
        "Давайте рассмотрим следующую модель:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ILmySACTvSA9"
      },
      "outputs": [],
      "source": [
        "from tensorflow import keras\n",
        "from tensorflow.keras import layers\n",
        "\n",
        "inputs = keras.Input(shape=(784,), name='digits')\n",
        "x = layers.Dense(64, activation='relu', name='dense_1')(inputs)\n",
        "x = layers.Dense(64, activation='relu', name='dense_2')(x)\n",
        "outputs = layers.Dense(10, activation='softmax', name='predictions')(x)\n",
        "\n",
        "model = keras.Model(inputs=inputs, outputs=outputs, name='3_layer_mlp')\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xPRqbd0yw8hY"
      },
      "source": [
        "Хоть это и необязательно, давайте обучим эту модель, чтобы она имела веса и состояние оптимизатора которые можно сохранить.\n",
        "Конечно вы можете сохранить модель, которую вы не обучали, но очевидно это не так интересно."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gCygTeGQw74g"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255\n",
        "\n",
        "model.compile(loss='sparse_categorical_crossentropy',\n",
        "              optimizer=keras.optimizers.RMSprop())\n",
        "history = model.fit(x_train, y_train,\n",
        "                    batch_size=64,\n",
        "                    epochs=1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "htnmbhz-iOwh"
      },
      "outputs": [],
      "source": [
        "# Сохраняем прогнозы для будущих проверок\n",
        "predictions = model.predict(x_test)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "opP1KROHwWwd"
      },
      "source": [
        "### Сохранение всей модели\n",
        "\n",
        "Вы можете сохранить модель построенную с Functional API в один файл. Позже вы можете восстановить ту же модель из этого файла, даже если у вас больше нет доступа к исходному коду.\n",
        "\n",
        "Этот файл включает:\n",
        "\n",
        "- Архитектуру модели\n",
        "- Значения весов модели (которые были получены во время обучения)\n",
        "- Конфигурацию обучения модели (то, что вы передаете `compile`), если таковая есть\n",
        "- Оптимизатор и его состояние, если имеется (это позволит вам продолжить обучение с места где вы остановились)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HqHvq6Igw3wx"
      },
      "outputs": [],
      "source": [
        "# Сохранение модели\n",
        "model.save('path_to_my_model.h5')\n",
        "\n",
        "# Восстановление в точности той же модели из файла\n",
        "new_model = keras.models.load_model('path_to_my_model.h5')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mmIcF6UOItJE"
      },
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "# Проверим что состояние сохранилось\n",
        "new_predictions = new_model.predict(x_test)\n",
        "np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)\n",
        "\n",
        "# Заметим, что состояние оптимизатора также сохранилось:\n",
        "# вы можете продолжить обучение с места где вы остановились."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-WEPW3n8ICyz"
      },
      "source": [
        "### Экспорт в SavedModel\n",
        "\n",
        "Вы можете также экспортировать всю модель в формат TensorFlow `SavedModel`. `SavedModel` является самостоятельным форматом сериализации для объектов TensorFlow, поддерживаемым TensorFlow serving, а также отличным от Python реализациям TensorFlow."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cKASRTKCU5nv"
      },
      "outputs": [],
      "source": [
        "# Экспортируем модель в SavedModel\n",
        "model.save('path_to_saved_model', save_format='tf')\n",
        "\n",
        "# Восстанавливаем в точности ту же модель\n",
        "new_model = keras.models.load_model('path_to_saved_model')\n",
        "\n",
        "# Проверяем что состояние сохранилось\n",
        "new_predictions = new_model.predict(x_test)\n",
        "np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)\n",
        "\n",
        "# Заметим, что состояние оптимизатора также сохранилось:\n",
        "# вы можете продолжить обучение с места где вы остановились."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "4AWgwkKWIhfj"
      },
      "source": [
        "Файлы `SavedModel` которые мы создали содержат:\n",
        "\n",
        "- Чекпоинт TensorFlow содержащий веса модели.\n",
        "- Прототип `SavedModel` содержащий граф TensorFlow лежащий в основе модели."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GkY8XP_XxgMI"
      },
      "source": [
        "### Сохранение только архитектуры\n",
        "\n",
        "Иногда вас интересует только архитектураа модели, и вам не нужно сохранять значения весов или оптимизатор. В этом случае вы можете получить \"config\" модели с помощью метода `get_config()`. Конфиг является словарем Python который позволяет вам воссоздать ту же модель -- инициализированную с нуля, не содержащую никакой информации полученнной ранее в результате обучения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "yQGGvo2Fw4o-"
      },
      "outputs": [],
      "source": [
        "config = model.get_config()\n",
        "reinitialized_model = keras.Model.from_config(config)\n",
        "\n",
        "# Заметьте что состояние модели не сохранено! Мы сохранили только архитектуру.\n",
        "new_predictions = reinitialized_model.predict(x_test)\n",
        "assert abs(np.sum(predictions - new_predictions)) > 0."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WsNBBvDgxsTS"
      },
      "source": [
        "В качестве альтернативы вы можете использовать `to_json()` и `from_json()`, которые используют строку JSON для хранения конфигурации вместо словаря Python. Это полезно при сохранении конфигурации на диск."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5a0z7_6XxqWV"
      },
      "outputs": [],
      "source": [
        "json_config = model.to_json()\n",
        "reinitialized_model = keras.models.model_from_json(json_config)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SGC7R6IIxy0o"
      },
      "source": [
        "### Сохранение только весов\n",
        "\n",
        "Иногда вас интересует только состояние модели -- значение ее весов -- а не архитектура. В этом случае вы можете получить значения весов в виде списка массивов Numpy с помощью `get_weights()`, и установить состояние модели с помощью `set_weights`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "B8tHwEvkxw5E"
      },
      "outputs": [],
      "source": [
        "weights = model.get_weights()  # Получает состояние модели.\n",
        "model.set_weights(weights)  # Устанавливает состояние модели."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ydwtw-u2x7xC"
      },
      "source": [
        "Вы можете комбинировать `get_config()`/`from_config()` и `get_weights()`/`set_weights()` для восстановления вашей модели в том же состоянии. Однако, в отличие от `model.save()` тут не включены конфигуарция обучения и оптимизатор. Вам нужно вновь вызвать `compile()` перед использованием модели для обучения."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LWVtuxtrx5lb"
      },
      "outputs": [],
      "source": [
        "config = model.get_config()\n",
        "weights = model.get_weights()\n",
        "\n",
        "new_model = keras.Model.from_config(config)\n",
        "new_model.set_weights(weights)\n",
        "\n",
        "# Проверяем что состояние сохранено\n",
        "new_predictions = new_model.predict(x_test)\n",
        "np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)\n",
        "\n",
        "# Заметим что оптимизатор не сохранился,\n",
        "# поэтому модель должна быть вновь скомпилирована перед обучением\n",
        "# (и оптимизатор начнет с пустого состояния)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "prk0GzwCyIYy"
      },
      "source": [
        "Альтернативой `get_weights()` и `set_weights(weights)` для работы с диском\n",
        "являются `save_weights(fpath)` и `load_weights(fpath)`.\n",
        "\n",
        "Вот пример сохранения на диск:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2irLnOUbyHlI"
      },
      "outputs": [],
      "source": [
        "# Сохраним конфигурацию JSON на диск\n",
        "json_config = model.to_json()\n",
        "with open('model_config.json', 'w') as json_file:\n",
        "    json_file.write(json_config)\n",
        "# Сохраним веса на диск\n",
        "model.save_weights('path_to_my_weights.h5')\n",
        "\n",
        "# Загрузим модель из 2 файлов которые мы сохранили\n",
        "with open('model_config.json') as json_file:\n",
        "    json_config = json_file.read()\n",
        "new_model = keras.models.model_from_json(json_config)\n",
        "new_model.load_weights('path_to_my_weights.h5')\n",
        "\n",
        "# Проверим, что состояние сохранилось\n",
        "new_predictions = new_model.predict(x_test)\n",
        "np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)\n",
        "\n",
        "# Заметим, что оптимизатор не сохранился."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KBxcFAPHyYi5"
      },
      "source": [
        "Но запомните, что самый простой и рекомендуемый способ следующий:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DE4b3ndNyQh3"
      },
      "outputs": [],
      "source": [
        "model.save('path_to_my_model.h5')\n",
        "del model\n",
        "model = keras.models.load_model('path_to_my_model.h5')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yKikmbdC3O_i"
      },
      "source": [
        "### Сохранение только весов с использованием чекпоинтов TensorFlow\n",
        "\n",
        "Заметьте, что `save_weights` может создавать файлы как в формате Keras HDF5,\n",
        "так и в [формате TensorFlow Checkpoint](https://www.tensorflow.org/api_docs/python/tf/train/Checkpoint). Формат подразумевается из расширения вашего файла: если это \".h5\" или \".keras\", фреймворк использует формат Keras HDF5. Все остальное по умолчанию сводится к Checkpoint."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0pYKb6LV3h2l"
      },
      "outputs": [],
      "source": [
        "model.save_weights('path_to_my_tf_checkpoint')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZFwKv6JC3kyu"
      },
      "source": [
        "Для полной ясности формат может быть явно передан через аргумент `save_format` , который может принимать значение \"tf\" или \"h5\":"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "oN9vOaWU34lA"
      },
      "outputs": [],
      "source": [
        "model.save_weights('path_to_my_tf_checkpoint', save_format='tf')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xXgtNRCSyuIW"
      },
      "source": [
        "## Сохранение наследованных моделей"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mJqOn0snzCRy"
      },
      "source": [
        "Sequential и Functional модели это структуры данных представимые в виде DAG слоев. Будучи таковыми,\n",
        "они могут быть безопасно сериализованы и десериализованы.\n",
        "\n",
        "Наследованная модель отличается тем, что это не структура данных, это кусок кода. Архитектура модели определяется\n",
        "в теле метода `call`. Это значит, что архитектура модели не может быть безопасно сериализована. Для загрузки модели вам понадобится доступ к коду который ее создал (код подкласса модели). Альтернативно, вы можете сериализовать этот код как байткод (e.g. via pickling), но это не безопасно и в общем не переносимо.\n",
        "\n",
        "Для дополнительной информации об этих различиях смотри статью [\"Что такое символьные и императивные API в TensorFlow 2.0?\"](https://medium.com/tensorflow/what-are-symbolic-and-imperative-apis-in-tensorflow-2-0-dfccecb01021)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Pkwyu5dVz12P"
      },
      "source": [
        "Давайте рассммотрим следующую наследованную модель, с той же структурой, что и модель в первой части:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4Onp-8rGyeQG"
      },
      "outputs": [],
      "source": [
        "class ThreeLayerMLP(keras.Model):\n",
        "\n",
        "  def __init__(self, name=None):\n",
        "    super(ThreeLayerMLP, self).__init__(name=name)\n",
        "    self.dense_1 = layers.Dense(64, activation='relu', name='dense_1')\n",
        "    self.dense_2 = layers.Dense(64, activation='relu', name='dense_2')\n",
        "    self.pred_layer = layers.Dense(10, activation='softmax', name='predictions')\n",
        "\n",
        "  def call(self, inputs):\n",
        "    x = self.dense_1(inputs)\n",
        "    x = self.dense_2(x)\n",
        "    return self.pred_layer(x)\n",
        "\n",
        "def get_model():\n",
        "  return ThreeLayerMLP(name='3_layer_mlp')\n",
        "\n",
        "model = get_model()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wwT_YoKA0yQW"
      },
      "source": [
        "Во-первых, *никогда не использовавшаяся наследованная модель не может быть сохранена*.\n",
        "\n",
        "Это потому, что наследованная модель должна быть вызвана на некоторых данных, чтобы были созданы ее веса.\n",
        "\n",
        "Пока модель не была вызвана, она не знает ожидаемые размерности и тип входных данных\n",
        "и поэтому не может создать переменные весов. Вы можете помнить что в Functional модели из первой части, размеры и тип входных данных были определены заранее (с помощью `keras.Input(...)`) -- вот почему у Functional модели есть состояние сразу как создан ее экземпляр.\n",
        "\n",
        "Давайте обучим модель, чтобы дать ей состояние:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xqP4kIFN0fTZ"
      },
      "outputs": [],
      "source": [
        "(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()\n",
        "x_train = x_train.reshape(60000, 784).astype('float32') / 255\n",
        "x_test = x_test.reshape(10000, 784).astype('float32') / 255\n",
        "\n",
        "model.compile(loss='sparse_categorical_crossentropy',\n",
        "              optimizer=keras.optimizers.RMSprop())\n",
        "history = model.fit(x_train, y_train,\n",
        "                    batch_size=64,\n",
        "                    epochs=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rvGCpyX72HOC"
      },
      "source": [
        "Рекомендованный способ сохранения наследованной модели это использование `save_weights` для создания чекпоинта TensorFlow SavedModel, который будет содержать значения всех переменных ассоциированных с моделью:\n",
        "- Веса слоев\n",
        "- Состояние оптимизатора\n",
        "- Любые переменные связанные с метриками модели с состоянием (если такие есть)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "gMg87Tz01cxQ"
      },
      "outputs": [],
      "source": [
        "model.save_weights('path_to_my_weights', save_format='tf')"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "KOKNBojtsl0F"
      },
      "outputs": [],
      "source": [
        "# Сохраним прогнозы для проверки в будущем\n",
        "predictions = model.predict(x_test)\n",
        "# Также сохраним потери на первом пакете\n",
        "# чтобы позже проверить что состояние оптимизатора было сохранено\n",
        "first_batch_loss = model.train_on_batch(x_train[:64], y_train[:64])"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h2PM_PL1SzPo"
      },
      "source": [
        "Для восстановления вашей модели вам понадобится доступ к коду, который создал объект модели.\n",
        "\n",
        "Обратите внимание, что для восстановления состояния оптимизатора или любой метрики с состоянием, вам нужно\n",
        "скомпилировать модель (точно с теми же аргументами, что и раньше) и вызвать ее на каких-либо данных перед вызовом `load_weights`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "OOSGiSkHTERy"
      },
      "outputs": [],
      "source": [
        "# Восстановление модели\n",
        "new_model = get_model()\n",
        "new_model.compile(loss='sparse_categorical_crossentropy',\n",
        "                  optimizer=keras.optimizers.RMSprop())\n",
        "\n",
        "# Здесь инициализируем переменные используемые отпимизаторами,\n",
        "# так же как и переменные любых метрик с состоянием\n",
        "new_model.train_on_batch(x_train[:1], y_train[:1])\n",
        "\n",
        "# Загрузим состояние старой модели\n",
        "new_model.load_weights('path_to_my_weights')\n",
        "\n",
        "# Проверим что состояние сохранилось\n",
        "new_predictions = new_model.predict(x_test)\n",
        "np.testing.assert_allclose(predictions, new_predictions, rtol=1e-6, atol=1e-6)\n",
        "\n",
        "# Состояние отпимизатораа также сохранилось,\n",
        "# так что вы можете продолжить обучение с того места где остановились\n",
        "new_first_batch_loss = new_model.train_on_batch(x_train[:64], y_train[:64])\n",
        "assert first_batch_loss == new_first_batch_loss"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "2_XaE5Oqv7Rh"
      },
      "source": [
        "Вы достигли конца этого руководства! Оно охватывает все, что вам нужно знать о сохранении и сериализации моделей с помощью tf.keras в TensorFlow 2.0."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "save_and_serialize.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
